package cn.edu.cug.cs.gtl.docsrv.img.manager;

import cn.edu.cug.cs.gtl.common.Pair;
import cn.edu.cug.cs.gtl.docsrv.config.Configuration; 
import cn.edu.cug.cs.gtl.docsrv.img.querier.BlockQuerier;
import cn.edu.cug.cs.gtl.docsrv.img.querier.DefaultBlockQuerier;
import cn.edu.cug.cs.gtl.docsrv.img.updater.BlockUpdater;
import cn.edu.cug.cs.gtl.docsrv.img.updater.DefaultBlockUpdater;
import cn.edu.cug.cs.gtl.io.FileDataSplitter;
import cn.edu.cug.cs.gtl.mybatis.Session;
import cn.edu.cug.cs.gtl.mybatis.mapper.img.ImgAll;
import cn.edu.cug.cs.gtl.mybatis.mapper.img.ImgFile;
import cn.edu.cug.cs.gtl.mybatis.mapper.img.ImgItem;
import cn.edu.cug.cs.gtl.mybatis.mapper.img.*;
import cn.edu.cug.cs.gtl.mybatis.metadata.ImgUtils;
import cn.edu.cug.cs.gtl.protos.TimeValue;
import cn.edu.cug.cs.gtl.protoswrapper.TimeValueWrapper;
import org.locationtech.jts.geom.Envelope;

import java.io.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 本地文件系统版本的图像库管理器
 * 元数据ImgItem采用Sqlite管理，
 * sqlite3文件为docsrv.db，
 * 位于本地数据文件夹的根目录下，
 * 文件采用本地文件系统进行管理
 */
public class LocalImgManager extends ImgManager{
    /**
     * 构造函数
     *
     * @param session
     */
    public LocalImgManager(Session session) {
        super(session);
        File f = new File(Configuration.getManagerConfig().getLocalImgDataRoot());
        if(!f.exists()){
            f.mkdirs();
        }
        File fTmp = new File(Configuration.getManagerConfig().getLocalDataRoot()+File.separator+"tmp");
        if(!fTmp.exists()){
            fTmp.mkdirs();
        }
    }

    public String getDataRoot(){
        return Configuration.getManagerConfig().getLocalImgDataRoot();
    }
    /**
     * 将ImgUrl解析成localDir和localFileName
     * @param url  格式为  file:/data/tmp/imgExample.zip
     *                    file:D:/data/tmp/imgExample.zip
     * @return 解析后的结果为
     * localDir = /data/tmp
     * localFileName=imgExample.zip
     */
    private static  Pair<String,String> parseImgUrl(String url){
        String[] ss = cn.edu.cug.cs.gtl.io.File.split(url);
        String remoteFileName = ss[ss.length - 1];
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 1; i < ss.length - 1; i++) {
            stringBuilder.append(File.separator);
            stringBuilder.append(ss[i]);
        }

        String remoteDir = stringBuilder.toString();
        //如果是在Windows下，需要处理盘符
        String [] ws = ss[0].split(":");
        if(ws.length>1){
            if(!ws[1].isEmpty())
                remoteDir = ws[1]+":"+remoteDir;
        }
        Pair<String,String> p = new Pair<>(remoteDir,remoteFileName);
        return p;
    }

    /**
     *
     * @param imgItem
     * @param imgFile
     * @return
     * @throws Exception
     */
    protected static boolean writeToFileSystem(ImgItem imgItem, ImgFile imgFile) throws Exception{
        //写入本地文件系统
        Pair<String, String> p = parseImgUrl(imgItem.getImgUrl());
        File f = new File(p.first()+File.separator+p.second());
        OutputStream outputStream = new FileOutputStream(f);
        outputStream.write(imgFile.getImgRawBytes());
        outputStream.close();
        return true;
    }

    /**
     *
     * @param localDir
     * @param localFileName
     * @return
     * @throws Exception
     */
    protected static ImgFile readFromFileSystem(String localDir, String localFileName) throws Exception{
        File f = new File(localDir +File.separator+ localFileName);
        InputStream inputStream = new FileInputStream(f);
        int len = (int)f.length();
        ImgFile imgFile = new ImgFile();
        byte[] bytes = new byte[len];
        inputStream.read(bytes);
        imgFile.setImgRawBytes(bytes);
        inputStream.close();
        return imgFile;
    }

    /**
     * 将一个由imgItem+imgFile组成的图像文档插入数据库
     *
     * @param imgItem 图像元数据信息,
     *                其中的imgUrl一定要设置值，形式如下
     *                file:/data/tmp/imgExample.zip
     * @param imgFile 图像文件内容
     * @return
     */
    @Override
    public boolean insert(ImgItem imgItem, ImgFile imgFile) {
        try {
            writeToFileSystem(imgItem,imgFile);
            //写入元数据
            ImgItemMapper imgItemMapper = session.getMapper(ImgItemMapper.class);
            imgItemMapper.insert(imgItem);
            session.commit();
            return true;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 将ImgAll分解成ImgItem和ImgFile，
     * 然后将ImgItem插入数据表IMG_ITEM，
     * 将ImgFile插入数据表IMG_FILE，
     * @param imgAll
     * @return
     */
    @Override
    public boolean insert(ImgAll imgAll) {
        Pair<ImgItem,ImgFile> p = ImgUtils.fromImgAll(imgAll);
        return insert(p.getFirst(),p.getSecond());
    }

    /**
     * 读取本地目录中的所有图像文档，将其插入图像库中
     *
     * @param localDir 本地目录
     * @return 成功返回true, 否则返回false
     */
    @Override
    public boolean insert(String localDir) {
        try {

            File file = new File(localDir);
            if (!file.isDirectory())
                return false;
            File parent = file.getParentFile();

            List<ImgCatalog> catalogs = localDirToCatalogs(localDir);
            for(ImgCatalog imgCatalog: catalogs){
                file = new File(parent.getAbsolutePath()+File.separator+imgCatalog.getCatalogName());
                if(!file.exists() || !file.isDirectory())
                    continue;

                File[] files = file.listFiles();
                for (File f : files) {
                    if(f.isDirectory()){
                        continue;
                    }
                    Pair<ImgItem,ImgFile> p = parseImgFiles(f);

                    if(p==null)  continue;

                    //设置IMGItem
                    ImgItemMapper imgItemMapper = session.getMapper(ImgItemMapper.class);
                    p.first().setImgInternalId(imgItemMapper.maxInternalId().add(BigDecimal.ONE));
                    StringBuilder imgUrl=new StringBuilder();
                    imgUrl.append("file:");
                    imgUrl.append(Configuration.getManagerConfig().getLocalImgDataRoot());
                    imgUrl.append(File.separator);
                    imgUrl.append(f.getName().replace("img","zip").trim());
                    p.first().setImgUrl(imgUrl.toString());
                    p.first().setImgCatalog(imgCatalog.getCatalogInternalId());
                    imgItemMapper.insert(p.first());
                    session.commit();

                    writeToFileSystem(p.first(),p.second());
                }
            }

        }
        catch (Exception e){
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 读取本地目录中的所有图像文档，将其插入图像库中
     * 不管Catalog相关信息的处理，直接插入数据库中
     * @param localDir 本地目录
     * @return 成功返回true, 否则返回false
     */
    public boolean insertWithoutCatalog(String localDir) {
        try {

            File file = new File(localDir);
            if (!file.isDirectory())
                return false;
            File[] files = file.listFiles();
            for (File f : files) {
                if(f.isDirectory()){
                    return insert(f.getCanonicalPath());
                }

                Pair<ImgItem,ImgFile> p = parseImgFiles(f);

                if(p==null)  continue;

                //设置IMGItem
                ImgItemMapper imgItemMapper = session.getMapper(ImgItemMapper.class);
                p.first().setImgInternalId(BigDecimal.valueOf(imgItemMapper.maxInternalId().doubleValue()+1));
                StringBuilder imgUrl=new StringBuilder();
                imgUrl.append("file:");
                imgUrl.append(Configuration.getManagerConfig().getLocalImgDataRoot());
                imgUrl.append(File.separator);
                imgUrl.append(f.getName().replace("img","zip").trim());
                p.first().setImgUrl(imgUrl.toString());

                imgItemMapper.insert(p.first());
                session.commit();

                writeToFileSystem(p.first(),p.second());
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 根据imgInternalId删除数据库中对应的图像文档记录
     *
     * @param imgItem 必须填充imgInternalId和imgUrl
     * @return
     */
    @Override
    public boolean delete(ImgItem imgItem) {
        try{
            ImgItemMapper imgItemMapper = session.getMapper(ImgItemMapper.class);
            imgItemMapper.deleteByPrimaryKey(imgItem.getImgInternalId());
            session.commit();
            Pair<String,String> p = parseImgUrl(imgItem.getImgUrl());
            File f = new File(p.first()+File.separator+p.second());
            if(f.exists())
                f.delete();
            return true;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 根据Id删除图像库中的元数据和内容数据
     * @param imgAll 必须填充imgInternalId和imgUrl
     * @return
     */
    @Override
    public boolean delete(ImgAll imgAll) {
        Pair<ImgItem,ImgFile> p = ImgUtils.fromImgAll(imgAll);
        return delete(p.first());
    }

    /**
     * 根据imgItem+imgFile更新数据库中的图像文档记录
     *
     * @param imgItem 该对象的imgInternalId必须填充,
     *                imgUrl必须填充
     * @param imgFile 更新文档文件的内容
     * @return 更新成功返回true, 否则返回false
     */
    @Override
    public boolean update(ImgItem imgItem, ImgFile imgFile) {
        try{
            ImgItemMapper imgItemMapper = session.getMapper(ImgItemMapper.class);
            imgItemMapper.updateByPrimaryKey(imgItem);

            Pair<String,String> p = parseImgUrl(imgItem.getImgUrl());
            File f = new File(p.first()+File.separator+p.second());
            boolean b = f.exists();
            if(b) f.delete();
            return writeToFileSystem(imgItem,imgFile);
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }

    /**
     *根据ImgAll中的内容更新图像库中对应的信息
     * @param imgAll 该对象的imgInternalId必须填充
     * @return
     */
    @Override
    public boolean update(ImgAll imgAll) {
        Pair<ImgItem,ImgFile> p = ImgUtils.fromImgAll(imgAll);
        return update(p.first(),p.second());
    }

    /**
     * 根据ID查询图像块信息
     * @param imgInternalId
     * @return
     */
    @Override
    public ImgAll queryByInternalId(BigDecimal imgInternalId) {
        try{
            ImgItemMapper imgItemMapper = session.getMapper(ImgItemMapper.class);
            ImgItem imgItem = imgItemMapper.selectByPrimaryKey(imgInternalId);
            Pair<String,String> p = parseImgUrl(imgItem.getImgUrl());


            ImgFile imgFile = readFromFileSystem(p.first(),p.second());
            imgFile.setImgInternalId(imgInternalId);

            ImgAll imgAll = ImgUtils.toImgAll(imgItem,imgFile);
            return imgAll;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据英文标题查询图像内容
     * @param imgEnTitle
     * @return
     */
    @Override
    public List<ImgAll> queryByEnTitle(String imgEnTitle){
        List<ImgAll> res = new ArrayList<>();
        try{
            ImgItemMapper imgItemMapper = session.getMapper(ImgItemMapper.class);
            List<ImgItem> imgItems = imgItemMapper.selectByEnTitle(imgEnTitle);
            for(ImgItem imgItem: imgItems){
                Pair<String,String> p = parseImgUrl(imgItem.getImgUrl());
                ImgFile imgFile = readFromFileSystem(p.first(),p.second());
                imgFile.setImgInternalId(imgItem.getImgInternalId());

                ImgAll imgAll = ImgUtils.toImgAll(imgItem,imgFile);
                res.add(imgAll);
            }
            return res;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据中文标题查询图像内容
     * @param imgCnTitle  文档注释
     * @return
     */
    @Override
    public List<ImgAll> queryByComment(String imgCnTitle) {
        List<ImgAll> res = new ArrayList<>();
        try{
            ImgItemMapper imgItemMapper = session.getMapper(ImgItemMapper.class);
            List<ImgItem> imgItems = imgItemMapper.selectByCnTitle(imgCnTitle);
            for(ImgItem imgItem: imgItems){
                Pair<String,String> p = parseImgUrl(imgItem.getImgUrl());
                ImgFile imgFile = readFromFileSystem(p.first(),p.second());
                imgFile.setImgInternalId(imgItem.getImgInternalId());

                ImgAll imgAll = ImgUtils.toImgAll(imgItem,imgFile);
                res.add(imgAll);
            }
            return res;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据类型查询图像信息
     * @param imgType
     * @return
     */
    @Override
    public List<ImgAll> queryByType(String imgType) {
        List<ImgAll> res = new ArrayList<>();
        try{
            ImgItemMapper imgItemMapper = session.getMapper(ImgItemMapper.class);
            List<ImgItem> imgItems = imgItemMapper.selectByType(imgType);
            for(ImgItem imgItem: imgItems){
                Pair<String,String> p = parseImgUrl(imgItem.getImgUrl());
                ImgFile imgFile = readFromFileSystem(p.first(),p.second());
                imgFile.setImgInternalId(imgItem.getImgInternalId());
                ImgAll imgAll = ImgUtils.toImgAll(imgItem,imgFile);
                res.add(imgAll);
            }
            return res;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据添加入库时间查询图像
     * @param begin 开始时间
     * @param end  结束时间
     * @return
     */
    @Override
    public List<ImgAll> queryByAppendDate(TimeValue begin, TimeValue end) {
        List<ImgAll> res = new ArrayList<>();
        try{
            ImgItemMapper imgItemMapper = session.getMapper(ImgItemMapper.class);
            List<ImgItem> imgItems =  imgItemMapper.selectByAppendDate(TimeValueWrapper.toUtilDate(begin),TimeValueWrapper.toUtilDate(end));
            for(ImgItem imgItem: imgItems){
                Pair<String,String> p = parseImgUrl(imgItem.getImgUrl());
                ImgFile imgFile = readFromFileSystem(p.first(),p.second());
                imgFile.setImgInternalId(imgItem.getImgInternalId());
                ImgAll imgAll = ImgUtils.toImgAll(imgItem,imgFile);
                res.add(imgAll);
            }
            return res;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据传入参数在图像库中进行查询
     *       try {
     *             Map<String,Object> p = new LinkedHashMap<String,Object>();
     *             p.put("imgInternalId",BigDecimal.valueOf(1));
     *             List<ImgItem> r1 = this.query(p);
     *             p.clear();
     *             p.put("imgEnTile","aaa");
     *             p.put("imgCnTile","zhangsan");
     *             List<ImgItem> r2 = this.query(p);
     *             boolean b = r1.size()==r2.size();
     *         }
     *         catch (Exception e){
     *             e.printStackTrace();
     *         }
     * @param parameters
     *     BigDecimal imgInternalId;
     *
     *     String imgEnTitle;
     *
     *     String imgCnTitle;
     *
     *     String imgUuid;
     *
     *     String imgVersion;
     *
     *     String imgKeywords;
     *
     *     String imgAbstract;
     *
     *     String imgContent;
     *
     *     String imgType;
     *
     *     String imgAuthors;
     *
     *     String imgAffiliation;
     *
     *     String imgTags;
     *
     *     String imgUrl;
     *
     *     Date imgAppendDate;
     *
     *     Date imgUpdateDate;
     *
     *     Date imgCreateDate;
     *
     *     String imgMemo;
     *
     *     BigDecimal imgCatalog;
     *
     *     String imgExtent;
     *
     *     byte[] imgRawBytes;
     * @return 返回查询到的文档。
     */
    @Override
    public List<ImgAll> query(Map<String, Object> parameters) {

        List<ImgAll> res = new ArrayList<>();
        try{
            ImgItemMapper imgItemMapper = session.getMapper(ImgItemMapper.class);
            List<ImgItem> imgItems =  imgItemMapper.query(parameters);
            for(ImgItem imgItem: imgItems){
                Pair<String,String> p = parseImgUrl(imgItem.getImgUrl());
                ImgFile imgFile = readFromFileSystem(p.first(),p.second());
                imgFile.setImgInternalId(imgItem.getImgInternalId());
                ImgAll imgAll = ImgUtils.toImgAll(imgItem,imgFile);
                res.add(imgAll);
            }
            return res;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取图像块ImageBlock级别的查询器
     *
     * @return
     */
    @Override
    public BlockQuerier getBlockQuerier() {
        return new DefaultBlockQuerier(this);
    }

    /**
     * 获取图像块ImageBlock级别的更新器
     * @return
     */
    @Override
    public BlockUpdater getBlockUpdater() {
        return new DefaultBlockUpdater(this);
    }

    /**
     * 查询得到所有ImageBlock的边界矩形
     * @return
     */
    @Override
    public  List<Pair<BigDecimal, Envelope>> queryAllEnvelopes(){
        try {
            ImgItemMapper imgItemMapper = session.getMapper(ImgItemMapper.class);
            List<LinkedHashMap<String, Object>> ls = imgItemMapper.selectAllInternalIdAndExtents();
            System.out.println(ls.size());
            List<Pair<BigDecimal, Envelope>> r = new ArrayList<>();
            for (LinkedHashMap<String, Object> l: ls){
                BigDecimal id = (BigDecimal)l.get("IMG_INTERNAL_ID");
                String coords = (String)l.get("IMG_EXTENT");
                String [] cs = coords.split(FileDataSplitter.COMMA.getDelimiter());
                double minX = Double.valueOf(cs[0]).doubleValue();
                double maxX = Double.valueOf(cs[1]).doubleValue();
                double minY = Double.valueOf(cs[2]).doubleValue();
                double maxY = Double.valueOf(cs[3]).doubleValue();
                Envelope envelope = new Envelope(minX,maxX,minY,maxY);
                r.add(new Pair<>(id,envelope));
            }
            return r;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

}
